#!/usr/bin/env/ python

import sys
import os, pwd
import nids
import argparse
import datetime
import re
import string
import time
import textwrap

from datetime import datetime

try:
	from http_parser.parser import HttpParser
except ImportError:
	from http_parser.pyparser import HttpParser

# Global variables
NOTROOT = "nobody"  
END_STATES = (nids.NIDS_CLOSE, nids.NIDS_TIMEOUT, nids.NIDS_RESET)
DEFAULT_FORMAT = '$shost -> $dhost: $url'
DEFAULT_FILTER = ''
DEBUG = False
FORMAT = None

def parseClientData(subDict, data, dataLength): 
	
	parser = HttpParser()
	parser.execute(data, dataLength)

	subDict['status_code'] = parser.get_status_code()

	for h in parser.get_headers().items():
		subDict[re.sub('-', '_', h[0].lower())] = h[1]

def parseServerData(subDict, data, dataLength): 
	
	parser = HttpParser()
	parser.execute(data, dataLength)

	subDict['url'] = parser.get_url()
	subDict['path'] = parser.get_path()
	subDict['method'] = parser.get_method()
	subDict['version'] = parser.get_version()

	for h in parser.get_headers().items():
		subDict[re.sub('-', '_', h[0].lower())] = h[1]
	

def createSubstitutionDictionary(tcp):

	((src, sport), (dst, dport)) = tcp.addr
	date = datetime.now()

	subDict = {
		'shost' : src, 'sport' : sport,
		'dhost' : dst, 'dport' : dport,
		'datetime' : date.ctime(),
		'timestamp' : int(time.mktime(date.timetuple()))
	}

	parseClientData(subDict, tcp.client.data[:tcp.client.count], tcp.client.count)
	parseServerData(subDict, tcp.server.data[:tcp.server.count], tcp.server.count)

	if DEBUG:
		print subDict

	return subDict


def printHttpStream(tcp):

	subDict = createSubstitutionDictionary(tcp)
	print FORMAT.safe_substitute(subDict)


def handleTcpStream(tcp):

	if tcp.nids_state == nids.NIDS_JUST_EST:
		((src, sport), (dst, dport)) = tcp.addr
		if dport in (80, 8000, 8080, 443, 8888):
			tcp.client.collect = 1
			tcp.server.collect = 1

	elif tcp.nids_state == nids.NIDS_DATA:
		# keep all of the stream's new data
		tcp.discard(0)

	elif tcp.nids_state in END_STATES:
		printHttpStream(tcp)

 
def parseArguments():

	parser = argparse.ArgumentParser(
		description="A very basic http sniffer implemented using libnids and htttp_parser",
		formatter_class=argparse.RawDescriptionHelpFormatter,
		epilog=textwrap.dedent('''\
		general format keys:
		  $shost               source host
		  $sport               source port
		  $dhost               destination host
		  $dport               destination port
		  $url                 destination url
		  $method              HTTP method
		  $status_code         HTTP status code
		  $path                HTTP path
		  $version             HTTP version
		  $datetime            date and time in ctime() format
		  $timestamp           date and time in timestamp

		headers format keys:
		  The headers keys are format with $ + headner name in lowercase and changing '-' for '_'. 
		  For example: 
		    'Host' -> '$host'
		    'Content-Length' -> '$content_length'
		
		Happy sniffing! ^.^
		''')
	)
 	group = parser.add_mutually_exclusive_group()

	group.add_argument("--filename", help="read a tcp stream from a file")
	group.add_argument("--device", help="set the devide to sniff")
	parser.add_argument("--format", help="set the output format (default: '%s')" % DEFAULT_FORMAT, default=DEFAULT_FORMAT)
	parser.add_argument("--filter", help="set the filter (see man tcpdump)", default=DEFAULT_FILTER)
	parser.add_argument("--debug", help="activate debug mode", action='store_true')

	return parser.parse_args()


def main(*args):

	global FORMAT
	global DEBUG

	# Reading the arguments
	args = parseArguments()

	# Output format
	FORMAT = string.Template(args.format)

	# Debug mode
	DEBUG = args.debug

	# Configuring nids options
	nids.param("pcap_filter", args.filter)
	nids.param("scan_num_hosts", 0)		  	# disable portscan detection
	nids.chksum_ctl([('0.0.0.0/0', False)]) # disable checksumming

	if args.filename:
		nids.param("filename", args.filename)
	elif args.device:
		nids.param("device", args.device)	

	# Initializing libnids 
	nids.init()

	# Changing the user to a non root user
	(uid, gid) = pwd.getpwnam(NOTROOT)[2:4]
	os.setgroups([gid,])
	os.setgid(gid)
	os.setuid(uid)
	if 0 in [os.getuid(), os.getgid()] + list(os.getgroups()):
		print "error - drop root, please!"
		sys.exit(1)

	nids.register_tcp(handleTcpStream)
	print "Sniffing traffic (pid %d)..." % os.getpid() 

	# Loop forever (network device), or until EOF (pcap file)
	# Note that an exception in the callback will break the loop!
	try:
		nids.run()
	except nids.error, e:
		print "nids/pcap error:", e
	except Exception, e:
		print "misc. exception (runtime error in user callback?):", e
	except KeyboardInterrupt:
		print "Keyboard Interruption!. Exiting."
		sys.exit(1)


if __name__ == '__main__' : main (sys.argv)
